#!/usr/bin/env python

#
# font-awesome-to-png.py
#
# Exports Font Awesome icons as PNG images.
#
# Copyright (c) 2012-2014 Michal Wojciechowski (http://odyniec.net/)
#
# Font Awesome - http://fortawesome.github.com/Font-Awesome
#

import sys, argparse, re
from os import path, access, R_OK
from PIL import Image, ImageFont, ImageDraw

# Support Unicode literals with both Python 2 and 3
if sys.version < '3':
    import codecs
    def u(x):
        return codecs.unicode_escape_decode(x)[0]

    def uchr(x):
        return unichr(x)
else:
    def u(x):
        return x

    def uchr(x):
        return chr(x)

# Mapping of icon names to character codes
icons = {
    "appreciate":u('\\ue644'),"check":u('\\ue645'),"close":u('\\ue646'),"edit":u('\\ue649'),"emoji":u('\\ue64a'),"favorfill":u('\\ue64b'),"favor":u('\\ue64c'),"loading":u('\\ue64f'),"locationfill":u('\\ue650'),"location":u('\\ue651'),"phone":u('\\ue652'),"roundcheckfill":u('\\ue656'),"roundcheck":u('\\ue657'),"roundclosefill":u('\\ue658'),"roundclose":u('\\ue659'),"roundrightfill":u('\\ue65a'),"roundright":u('\\ue65b'),"search":u('\\ue65c'),"taxi":u('\\ue65d'),"timefill":u('\\ue65e'),"time":u('\\ue65f'),"unfold":u('\\ue661'),"warnfill":u('\\ue662'),"warn":u('\\ue663'),"camerafill":u('\\ue664'),"camera":u('\\ue665'),"commentfill":u('\\ue666'),"comment":u('\\ue667'),"likefill":u('\\ue668'),"like":u('\\ue669'),"notificationfill":u('\\ue66a'),"notification":u('\\ue66b'),"order":u('\\ue66c'),"samefill":u('\\ue66d'),"same":u('\\ue66e'),"deliver":u('\\ue671'),"evaluate":u('\\ue672'),"pay":u('\\ue673'),"send":u('\\ue675'),"shop":u('\\ue676'),"ticket":u('\\ue677'),"back":u('\\ue679'),"cascades":u('\\ue67c'),"discover":u('\\ue67e'),"list":u('\\ue682'),"more":u('\\ue684'),"scan":u('\\ue689'),"settings":u('\\ue68a'),"questionfill":u('\\ue690'),"question":u('\\ue691'),"shopfill":u('\\ue697'),"form":u('\\ue699'),"pic":u('\\ue69b'),"filter":u('\\ue69c'),"footprint":u('\\ue69d'),"top":u('\\ue69e'),"pulldown":u('\\ue69f'),"pullup":u('\\ue6a0'),"right":u('\\ue6a3'),"refresh":u('\\ue6a4'),"moreandroid":u('\\ue6a5'),"deletefill":u('\\ue6a6'),"refund":u('\\ue6ac'),"cart":u('\\ue6af'),"qrcode":u('\\ue6b0'),"remind":u('\\ue6b2'),"delete":u('\\ue6b4'),"profile":u('\\ue6b7'),"home":u('\\ue6b8'),"cartfill":u('\\ue6b9'),"discoverfill":u('\\ue6ba'),"homefill":u('\\ue6bb'),"message":u('\\ue6bc'),"addressbook":u('\\ue6bd'),"link":u('\\ue6bf'),"lock":u('\\ue6c0'),"unlock":u('\\ue6c2'),"vip":u('\\ue6c3'),"weibo":u('\\ue6c4'),"activity":u('\\ue6c5'),"friendaddfill":u('\\ue6c9'),"friendadd":u('\\ue6ca'),"friendfamous":u('\\ue6cb'),"friend":u('\\ue6cc'),"goods":u('\\ue6cd'),"selection":u('\\ue6ce'),"explore":u('\\ue6d2'),"present":u('\\ue6d3'),"squarecheckfill":u('\\ue6d4'),"square":u('\\ue6d5'),"squarecheck":u('\\ue6d6'),"round":u('\\ue6d7'),"roundaddfill":u('\\ue6d8'),"roundadd":u('\\ue6d9'),"add":u('\\ue6da'),"notificationforbidfill":u('\\ue6db'),"explorefill":u('\\ue6dd'),"fold":u('\\ue6de'),"game":u('\\ue6df'),"redpacket":u('\\ue6e0'),"selectionfill":u('\\ue6e1'),"similar":u('\\ue6e2'),"appreciatefill":u('\\ue6e3'),"infofill":u('\\ue6e4'),"info":u('\\ue6e5'),"forwardfill":u('\\ue6ea'),"forward":u('\\ue6eb'),"rechargefill":u('\\ue6ec'),"recharge":u('\\ue6ed'),"vipcard":u('\\ue6ee'),"voice":u('\\ue6ef'),"voicefill":u('\\ue6f0'),"friendfavor":u('\\ue6f1'),"wifi":u('\\ue6f2'),"share":u('\\ue6f3'),"wefill":u('\\ue6f4'),"we":u('\\ue6f5'),"lightauto":u('\\ue6f6'),"lightforbid":u('\\ue6f7'),"lightfill":u('\\ue6f8'),"camerarotate":u('\\ue6f9'),"light":u('\\ue6fa'),"barcode":u('\\ue6fb'),"flashlightclose":u('\\ue6fc'),"flashlightopen":u('\\ue6fd'),"searchlist":u('\\ue6fe'),"service":u('\\ue6ff'),"sort":u('\\ue700'),"down":u('\\ue703'),"mobile":u('\\ue704'),"mobilefill":u('\\ue705'),"copy":u('\\ue706'),"countdownfill":u('\\ue707'),"countdown":u('\\ue708'),"noticefill":u('\\ue709'),"notice":u('\\ue70a'),"upstagefill":u('\\ue70e'),"upstage":u('\\ue70f'),"babyfill":u('\\ue710'),"baby":u('\\ue711'),"brandfill":u('\\ue712'),"brand":u('\\ue713'),"choicenessfill":u('\\ue714'),"choiceness":u('\\ue715'),"clothesfill":u('\\ue716'),"clothes":u('\\ue717'),"creativefill":u('\\ue718'),"creative":u('\\ue719'),"female":u('\\ue71a'),"keyboard":u('\\ue71b'),"male":u('\\ue71c'),"newfill":u('\\ue71d'),"new":u('\\ue71e'),"pullleft":u('\\ue71f'),"pullright":u('\\ue720'),"rankfill":u('\\ue721'),"rank":u('\\ue722'),"bad":u('\\ue723'),"cameraadd":u('\\ue724'),"focus":u('\\ue725'),"friendfill":u('\\ue726'),"cameraaddfill":u('\\ue727'),"apps":u('\\ue729'),"paintfill":u('\\ue72a'),"paint":u('\\ue72b'),"picfill":u('\\ue72c'),"refresharrow":u('\\ue72d'),"colorlens":u('\\ue6e6'),"markfill":u('\\ue730'),"mark":u('\\ue731'),"presentfill":u('\\ue732'),"repeal":u('\\ue733'),"album":u('\\ue734'),"peoplefill":u('\\ue735'),"people":u('\\ue736'),"servicefill":u('\\ue737'),"repair":u('\\ue738'),"file":u('\\ue739'),"repairfill":u('\\ue73a'),"taoxiaopu":u('\\ue73b'),"weixin":u('\\ue612'),"attentionfill":u('\\ue73c'),"attention":u('\\ue73d'),"commandfill":u('\\ue73e'),"command":u('\\ue73f'),"communityfill":u('\\ue740'),"community":u('\\ue741'),"read":u('\\ue742'),"calendar":u('\\ue74a'),"cut":u('\\ue74b'),"magic":u('\\ue74c'),"backwardfill":u('\\ue74d'),"playfill":u('\\ue74f'),"stop":u('\\ue750'),"tagfill":u('\\ue751'),"tag":u('\\ue752'),"group":u('\\ue753'),"all":u('\\ue755'),"backdelete":u('\\ue756'),"hotfill":u('\\ue757'),"hot":u('\\ue758'),"post":u('\\ue759'),"radiobox":u('\\ue75b'),"rounddown":u('\\ue75c'),"upload":u('\\ue75d'),"writefill":u('\\ue760'),"write":u('\\ue761'),"radioboxfill":u('\\ue763'),"punch":u('\\ue764'),"shake":u('\\ue765'),"move":u('\\ue768'),"safe":u('\\ue769'),"activityfill":u('\\ue775'),"crownfill":u('\\ue776'),"crown":u('\\ue777'),"goodsfill":u('\\ue778'),"messagefill":u('\\ue779'),"profilefill":u('\\ue77a'),"sound":u('\\ue77b'),"sponsorfill":u('\\ue77c'),"sponsor":u('\\ue77d'),"upblock":u('\\ue77e'),"weblock":u('\\ue77f'),"weunblock":u('\\ue780'),"my":u('\\ue78b'),"myfill":u('\\ue78c'),"emojifill":u('\\ue78d'),"emojiflashfill":u('\\ue78e'),"flashbuyfill":u('\\ue78f'),"text":u('\\ue791'),"goodsfavor":u('\\ue794'),"musicfill":u('\\ue795'),"musicforbidfill":u('\\ue796'),"card":u('\\ue624'),"triangledownfill":u('\\ue79b'),"triangleupfill":u('\\ue79c'),"roundleftfill":u('\\ue79e'),"font":u('\\ue76a'),"title":u('\\ue82f'),"recordfill":u('\\ue7a4'),"record":u('\\ue7a6'),"cardboardfill":u('\\ue7a9'),"cardboard":u('\\ue7aa'),"formfill":u('\\ue7ab'),"coin":u('\\ue7ac'),"cardboardforbid":u('\\ue7af'),"circlefill":u('\\ue7b0'),"circle":u('\\ue7b1'),"attentionforbid":u('\\ue7b2'),"attentionforbidfill":u('\\ue7b3'),"attentionfavorfill":u('\\ue7b4'),"attentionfavor":u('\\ue7b5'),"titles":u('\\ue701'),"icloading":u('\\ue67a'),"full":u('\\ue7bc'),"mail":u('\\ue7bd'),"peoplelist":u('\\ue7be'),"goodsnewfill":u('\\ue7bf'),"goodsnew":u('\\ue7c0'),"medalfill":u('\\ue7c1'),"medal":u('\\ue7c2'),"newsfill":u('\\ue7c3'),"newshotfill":u('\\ue7c4'),"newshot":u('\\ue7c5'),"news":u('\\ue7c6'),"videofill":u('\\ue7c7'),"video":u('\\ue7c8'),"exit":u('\\ue7cb'),"skinfill":u('\\ue7cc'),"skin":u('\\ue7cd'),"moneybagfill":u('\\ue7ce'),"usefullfill":u('\\ue7cf'),"usefull":u('\\ue7d0'),"moneybag":u('\\ue7d1'),"redpacket_fill":u('\\ue7d3'),"subscription":u('\\ue7d4'),"loading1":u('\\ue633'),"github":u('\\ue692'),"global":u('\\ue7eb'),"settingsfill":u('\\ue6ab'),"back_android":u('\\ue7ed'),"expressman":u('\\ue7ef'),"evaluate_fill":u('\\ue7f0'),"group_fill":u('\\ue7f5'),"play_forward_fill":u('\\ue7f6'),"deliver_fill":u('\\ue7f7'),"notice_forbid_fill":u('\\ue7f8'),"fork":u('\\ue60c'),"pick":u('\\ue7fa'),"wenzi":u('\\ue6a7'),"ellipse":u('\\ue600'),"qr_code":u('\\ue61b'),"dianhua":u('\\ue64d'),"cuIcon":u('\\ue602'),"loading2":u('\\ue7f1'),"btn":u('\\ue601')
}


class ListAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        for icon in sorted(icons.keys()):
            print(icon)
        exit(0)


class ListUpdateAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        print("icons = {")
        for icon in sorted(icons.keys()):
            print(u'    "%s": u("\\u%x"),' % (icon, ord(icons[icon])))
        print("}")
        exit(0)


def export_icon(icon, size, filename, font, color):
    image = Image.new("RGBA", (size, size), color=(0,0,0,0))

    draw = ImageDraw.Draw(image)

    # Initialize font
    font = ImageFont.truetype(font, size)

    # Determine the dimensions of the icon
    width,height = draw.textsize(icons[icon], font=font)

    draw.text(((size - width) / 2, (size - height) / 2), icons[icon],
            font=font, fill=color)

    # Get bounding box
    bbox = image.getbbox()

    # Create an alpha mask
    imagemask = Image.new("L", (size, size), 0)
    drawmask = ImageDraw.Draw(imagemask)

    # Draw the icon on the mask
    drawmask.text(((size - width) / 2, (size - height) / 2), icons[icon],
        font=font, fill=255)

    # Create a solid color image and apply the mask
    iconimage = Image.new("RGBA", (size,size), color)
    iconimage.putalpha(imagemask)

    if bbox:
        iconimage = iconimage.crop(bbox)

    borderw = int((size - (bbox[2] - bbox[0])) / 2)
    borderh = int((size - (bbox[3] - bbox[1])) / 2)

    # Create output image
    outimage = Image.new("RGBA", (size, size), (0,0,0,0))
    outimage.paste(iconimage, (borderw,borderh))

    # Save file
    outimage.save(filename)


class LoadCSSAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        global icons
        icons = LoadCSSAction._load_css(values)

    @staticmethod
    def _load_css(filename):
        import tinycss
        new_icons = {}
        parser = tinycss.make_parser("page3")

        try:
            stylesheet = parser.parse_stylesheet_file(filename)
        except IOError:
            print >> sys.stderr, ("Error: CSS file (%s) can't be opened"
                % (filename))
            exit(1)

        is_icon = re.compile(u("\.fa-(.*):before,?"))
        for rule in stylesheet.rules:
            selector = rule.selector.as_css()
            for match in is_icon.finditer(selector):
                name = match.groups()[0]
                for declaration in rule.declarations:
                    if declaration.name == u"content":
                        val = declaration.value.as_css()
                        if val.startswith('"') and val.endswith('"'):
                            val = val[1:-1]
                        new_icons[name] = uchr(int(val[1:], 16))
        return new_icons


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
            description="Exports Font Awesome icons as PNG images.")

    parser.add_argument("icon", type=str, nargs="+",
            help="The name(s) of the icon(s) to export (or \"ALL\" for all icons)")
    parser.add_argument("--color", type=str, default="black",
            help="Color (HTML color code or name, default: black)")
    parser.add_argument("--filename", type=str,
            help="The name of the output file (it must end with \".png\"). If " +
            "all files are exported, it is used as a prefix.")
    parser.add_argument("--font", type=str, default="fontawesome-webfont.ttf",
            help="Font file to use (default: fontawesome-webfont.ttf)")
    parser.add_argument("--css", type=str, default="", action=LoadCSSAction,
            help="Path to the CSS file defining icon names (instead of the " +
            "predefined list)")
    parser.add_argument("--list", nargs=0, action=ListAction,
            help="List available icon names and exit")
    parser.add_argument("--list-update", nargs=0, action=ListUpdateAction,
            help=argparse.SUPPRESS)
    parser.add_argument("--size", type=int, default=16,
            help="Icon size in pixels (default: 16)")

    args = parser.parse_args()
    icon = args.icon
    size = args.size
    font = args.font
    color = args.color

    if args.font:
        if not path.isfile(args.font) or not access(args.font, R_OK):
            print >> sys.stderr, ("Error: Font file (%s) can't be opened"
                    % (args.font))
            exit(1)

    if args.icon == [ "ALL" ]:
        # Export all icons
        selected_icons = sorted(icons.keys())
    else:
        selected_icons = []

        # Icon name was given
        for icon in args.icon:
            # Strip the "icon-" prefix, if present
            if icon.startswith("icon-"):
                icon = icon[5:]

            if icon in icons:
                selected_icons.append(icon)
            else:
                print >> sys.stderr, "Error: Unknown icon name (%s)" % (icon)
                sys.exit(1)

    for icon in selected_icons:
        if len(selected_icons) > 1:
            # Exporting multiple icons -- treat the filename option as name prefix
            filename = (args.filename or "") + icon + ".png"
        else:
            # Exporting one icon
            if args.filename:
                filename = args.filename
            else:
                filename = icon + ".png"

        print("Exporting icon \"%s\" as %s (%ix%i pixels)" %
                (icon, filename, size, size))

        export_icon(icon, size, filename, font, color)
